package com.tong.hao.common.configuration.aspect;

import cn.buddha.jec.exception.TongBusinessResult;
import cn.buddha.jec.model.TongBaseRequest;
import cn.buddha.jec.model.TongBaseResponse;
import cn.buddha.jec.util.TongValidatorUtils;
import com.google.common.base.Stopwatch;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Validator;
import java.util.concurrent.TimeUnit;

/**
 * @Author 友野浩二
 * @ClassName ValidateRequestAspect
 * @Description // 切面: API接口数据校验
 * @Version 1.0 作废
 *
 * <pre>
 * @Lazy 注解: 容器一般都会在启动的时候实例化所有单实例bean，如果我们想要Spring在启动的时候延迟加载bean，需要用到这个注解。
 *              value值为true 或 false，默认true(延迟加载)，@Lazy(value = false)表示对象会在初始化的时候创建。
 * </pre>
 **/
@Aspect
@Component
@Lazy(value = false)
@Slf4j
public class HaoValidateRequestAspect {

    /**
     * 记录API接口业务逻辑代码的执行开始时间
     */
//    private static ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * @Author 友野浩二
     * @Param []
     * @return void
     * @Description // 定义切入点：要对哪些类中的哪些方法进行增强，进行切割，指的是被增强的方法。即要切哪些东西。如包、类。
     *
     * <pre>
     *      execution: 用来匹配执行方法的连接点。
     *      1- 语法结构: execution(方法修饰符        方法返回值类型[必填] 方法所属包和类     匹配方法名(方法中的形参) 方法申明抛出异常)
     *                   execution(modifiers-pattern?ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throw-pattern?)
     *      2- 各部分都支持通配符"*"来匹配全部(可以使用..表示当前包及子包);
     *         方法的形参支持两种通配符。
     *              "*": 代表[一个]任意类型的参数;
     *              "..": 代表零个或多个任意类型的参数.
     *
     *      当前方法的切入点是：com.tong.hao.online.service.controller包及子包下的所有类的 接收一个参数的方法
     *      "execution(* com.tong.hao.ac.service.controller..*.*(*))"
     *      【注意】：开发规范规定controller类的所有方法只能有一个参数。
     * </pre>
     */
    @Pointcut("@annotation(com.tong.hao.common.annotation.HaoValidate)")
    private void validator() {
        log.info("<======== entry HaoValidateRequestAspect validator ========>");
    }

    /**
     * @Author 友野浩二
     * @Description // 前置通知：在目标方法执行前调用，在该方法中完成API接口入参模型的数据校验(类似javax.validation.constraints.NotBlank这种校验)
     */
    @Before("validator()")
    public void doBeforeController(JoinPoint joinPoint) {
        log.info("<======== entry HaoValidateRequestAspect 前置通知 ========>");
        // 在前置通知中进行接口入参数据校验
        // 创建自动start的计时器(记录前置通知的耗时时间)
//        Stopwatch stopwatch = Stopwatch.createStarted();

        // 获取请求controller的属性
//        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
//        HttpServletRequest request = servletRequestAttributes.getRequest();
//
//        String requestURI = request.getRequestURI();
//        log.info("Hao URI({}) Start", requestURI);

//        Object[] args = joinPoint.getArgs();// 获取传入目标方法的参数对象
//        if (args[0] instanceof TongBaseRequest) {
//            TongBaseRequest tongBaseRequest = (TongBaseRequest) args[0];// 因为validator()方法的切入点配置的目标方法只有一个参数,所以只获取参数对象的第一个
//            log.info("request msg: {}", tongBaseRequest);// 打印请求报文
//            Validator validator = TongValidatorUtils.getValidator();
//            TongValidatorUtils.validateRequest(tongBaseRequest.getTongInHead(), validator);// 对请求头信息进行校验
//            TongValidatorUtils.validateRequest(tongBaseRequest, validator);// 对接口上送模型进行校验
//        }
//
//        log.info("<======== exit HaoValidateRequestAspect 前置通知, elapsed time is [{}] ms ========>",
//                stopwatch.elapsed(TimeUnit.MILLISECONDS));
//        startTime.set(System.currentTimeMillis());// 设置当前时间戳(业务逻辑代码开始执行的时间)
    }

    /**
     * @Author 友野浩二
     * @Description // 后置通知：在目标方法执行后调用，若目标方法出现异常，则不执行。
     *
     * <pre>
     *     @AfterReturning 注解属性说明：
     *          pointcut/value：这两个属性的作用是一样的，它们都属于指定切入点对应的表达式。当指定了
     *                          pointcut属性值后，value属性值会被覆盖；
     *          returning：该属性指定一个形参名，用于表示Advice方法中可定义与此同名的形参，该形参可用
     *                      于访问目标方法的返回值。除此之外，在Advice方法中定义该形参(代表目标方法的
     *                      返回值)时指定的类型，会限制目标方法必须返回指定类型的值或没有返回值。若声
     *                      明为Object，则意味着对目标方法的返回值不加限制。
     * </pre>
     **/
    @AfterReturning(pointcut = "validator()", returning = "responseValue")
    public void doAfterReturningController(Object responseValue) {
        log.info("<======== entry HaoValidateRequestAspect 后置通知: after returning ========>");

        // 获取请求controller的属性
//        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
//        HttpServletRequest request = servletRequestAttributes.getRequest();
//
//        String requestURI = request.getRequestURI();
//
//        Long costTime = System.currentTimeMillis() - startTime.get();// 业务逻辑代码执行的耗时时间
//        log.info("Hao URI({}) End, UseTime({}ms)", requestURI, costTime);

        // 创建自动start的计时器(对前置通知进行计时)
//        Stopwatch stopwatch = Stopwatch.createStarted();
//        if (responseValue instanceof TongBaseResponse) {
//            TongBaseResponse tongBaseResponse = (TongBaseResponse) responseValue;
//            tongBaseResponse.setTongOutHead(TongBusinessResult.success());
//        }
//        log.info("<======== exit HaoValidateRequestAspect 后置通知, elapsed time is [{}] ms ========>",
//                stopwatch.elapsed(TimeUnit.MILLISECONDS));
//        log.info("Hao URI({}) End, response msg: {}", requestURI, responseValue);
//        startTime.remove();
    }

    /**
     * @Author 友野浩二
     * @Description // 后置/最终通知：无论目标方法在执行过程中是否出现异常,都会在它之后调用
     **/
    @After("validator()")
    public void doAfterController() {
        log.info("<======== entry ValidateRequestAspect 后置通知: finally returning ========>");
    }

}
